package com.hwd.srb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hwd.srb.common.exception.BusinessException;
import com.hwd.srb.common.util.LendNoUtils;
import com.hwd.srb.core.enmu.LendStatusEnum;
import com.hwd.srb.core.enmu.ReturnMethodEnum;
import com.hwd.srb.core.enmu.TransTypeEnum;
import com.hwd.srb.core.hfb.HfbConst;
import com.hwd.srb.core.hfb.RequestHelper;
import com.hwd.srb.core.mapper.*;
import com.hwd.srb.core.pojo.bo.TransFlowBO;
import com.hwd.srb.core.pojo.entity.*;
import com.hwd.srb.core.pojo.vo.BorrowInfoApprovalVO;
import com.hwd.srb.core.pojo.vo.BorrowerDetailVO;
import com.hwd.srb.core.service.BorrowerService;
import com.hwd.srb.core.service.LendItemService;
import com.hwd.srb.core.service.LendService;
import com.hwd.srb.core.service.TransFlowService;
import com.hwd.srb.core.util.*;
import com.hwd.srb.core.util.adapter.AmountHelperAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author 黄伟东/Victor
 * @since 2022-03-10
 */
@Service
@Slf4j
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Resource
    private BorrowerMapper borrowerMapper;

    @Resource
    private BorrowerService borrowerService;

    @Resource
    private UserAccountMapper userAccountMapper;

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private TransFlowService transFlowService;

    @Resource
    private LendItemService lendItemService;

    @Resource
    private LendReturnMapper lendReturnMapper;

    @Resource
    private LendItemReturnMapper lendItemReturnMapper;

    @Resource
    private AmountHelperDispatch dispatcher;

    /**
     * 借款审核通过后，根据借款审核对象、借款信息创建标的
     *
     * @param borrowInfoApprovalVO 借款审核对象
     * @param borrowInfo           借款信息对象
     */
    @Override
    public void createLend(BorrowInfoApprovalVO borrowInfoApprovalVO, BorrowInfo borrowInfo) {

        // 创建lend标的对象
        Lend lend = new Lend();

        // 生成标的的邀约信息(准合同信息)
        // 填充借款审核信息
        lend.setLendInfo(borrowInfoApprovalVO.getLendInfo());
        lend.setTitle(borrowInfoApprovalVO.getTitle());
        lend.setStatus(LendStatusEnum.INVEST_RUN);
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setUserId(borrowInfo.getUserId());
        // 还款方式
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        // 生成标的编号
        lend.setLendNo(LendNoUtils.getLendNo());

        BigDecimal lendYearRate = borrowInfoApprovalVO.getLendYearRate()
                .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_DOWN);
        lend.setLendYearRate(lendYearRate);// 年化利率

        BigDecimal serviceRate = borrowInfoApprovalVO.getServiceRate()
                .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_HALF_DOWN);
        lend.setServiceRate(serviceRate);// 平台服务费率


        // 标的的起始时间，就是募资结束日期，也是起息日，也是标的开始的时间，放款日
        String lendStartDateStr = borrowInfoApprovalVO.getLendStartDate();
        LocalDate lendStartDate = LocalDate.parse(lendStartDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        lend.setLendStartDate(lendStartDate);

        // 标的的结束时间=标的起始时间+期数
        LocalDate lendEndDate = lendStartDate.plusMonths(borrowInfo.getPeriod());
        lend.setLendEndDate(lendEndDate);

        // 实际收益
        lend.setRealAmount(new BigDecimal("0"));

        // 最低投资额
        lend.setLowestAmount(new BigDecimal("100"));

        lend.setAmount(borrowInfo.getAmount());// 标的总额
        // 期数
        lend.setPeriod(borrowInfo.getPeriod());
        // 已投金额
        lend.setInvestAmount(new BigDecimal("0"));
        lend.setInvestNum(0);// 已投人数

        // 平台预期收益=标的总额*（服务年费率/12）*期数
        // 月年化利率 = 年化 / 12
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal("12"), 8, BigDecimal.ROUND_DOWN);
        BigDecimal expectAmount = lend.getAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));
        lend.setExpectAmount(expectAmount);

        // 标的审核时间
        lend.setCheckTime(LocalDateTime.now());
        // 发布时间
        lend.setPublishDate(LocalDateTime.now());
        // 创建时间
        lend.setCreateTime(LocalDateTime.now());
        //审核人
        lend.setCheckAdminId(1L);

        baseMapper.insert(lend);

    }

    /**
     * 查询标的列表
     *
     * @return java.util.List<com.hwd.srb.core.pojo.entity.Lend>
     */
    @Override
    public List<Lend> selectList() {

        return baseMapper.selectList(null);
    }

    /**
     * 根据标的id获取标的信息
     *
     * @param lendId 标的id
     * @return java.util.Map<java.lang.String, java.lang.Object>
     */
    @Override
    public Map<String, Object> getLendDetail(Long lendId) {

        Map<String, Object> lendDetail = new HashMap<>();

        // 查询标的信息
        Lend lend = baseMapper.selectById(lendId);

        // 查询借款人信息
        LambdaQueryWrapper<Borrower> borrowerQueryWrapper = new LambdaQueryWrapper<Borrower>()
                .eq(Borrower::getUserId, lend.getUserId());
        Borrower borrower = borrowerMapper.selectOne(borrowerQueryWrapper);
        BorrowerDetailVO borrowerDetailVO = borrowerService.show(borrower.getId());

        lendDetail.put("lend", lend);
        lendDetail.put("borrower", borrowerDetailVO);

        return lendDetail;
    }

    /**
     * 计算收益
     *
     * @param invest       投资金额
     * @param yearRate     年华利率，如：12%
     * @param totalMonth   期数
     * @param returnMethod 还款方式
     * @return java.math.BigDecimal
     */
    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalMonth, String returnMethod) {

        BigDecimal interestCount;

        AmountHelper helper = dispatcher.getAmountHelper(returnMethod);

        AmountHelperAdapter adapter = dispatcher.getAmountHelperAdapter(helper);

        interestCount = adapter.getInterestCount(invest, yearRate, totalMonth);

        // 判断还款方式
//        if (returnMethod.equals(ReturnMethodEnum.EQUALITY_CORPUS_AND_INTEREST.getReturnMethod())) {
//            // 第一种，等额本息
//            interestCount = Amount1Helper.getInterestCount(invest, yearRate, totalMonth);
//        }
//
//        if (returnMethod.equals(ReturnMethodEnum.EQUALITY_CORPUS.getReturnMethod())) {
//            // 第二种，等额本金
//            interestCount = Amount2Helper.getInterestCount(invest, yearRate, totalMonth);
//        }
//
//        if (returnMethod.equals(ReturnMethodEnum.PER_MONTH_INTEREST_AND_ONCE_CORPUS.getReturnMethod())) {
//            // 第三种，每月还息一次还本
//            interestCount = Amount3Helper.getInterestCount(invest, yearRate, totalMonth);
//        }
//
//        if (returnMethod.equals(ReturnMethodEnum.ONCE_CORPUS_AND_INTEREST.getReturnMethod())) {
//            // 第四种，一次还本还息
//            interestCount = Amount4Helper.getInterestCount(invest, yearRate, totalMonth);
//        }

        return interestCount;
    }

    /**
     * 满标放款
     *
     * @param lendId 标的id
     */
//    @Transactional
    @Override
    public void makeLoan(Long lendId) {

        log.info("第一步，进入放款流程");
        // 获取标的信息
        Lend lend = baseMapper.selectById(lendId);
        log.info("1 生成放款单号");
        String agentBillNo = LendNoUtils.getLoanNo();// 放款编号

        // 1、放款接口调用
        log.info("2 调用放款接口");
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("agentId", HfbConst.AGENT_ID);
        paramMap.put("agentProjectCode", lend.getLendNo());// 标的编号
        paramMap.put("agentBillNo", agentBillNo);

        // 平台收益，放款扣除，借款人借款实际金额=借款金额-平台收益
        // 月年化
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal("12"), 8, BigDecimal.ROUND_DOWN);
        // 平台实际收益 = 已投金额 * 月年化 * 标的期数
        BigDecimal realAmount = lend.getInvestAmount().multiply(monthRate).multiply(new BigDecimal(lend.getPeriod()));

        paramMap.put("mchFee", realAmount); //商户手续费(平台实际收益)
        paramMap.put("timestamp", RequestHelper.getTimestamp());
        paramMap.put("sign", RequestHelper.getSign(paramMap));// 验签

        log.info("开始执行远程调用汇付宝放款接口，放款参数：" + JSONObject.toJSONString(paramMap));
        // 发送同步远程调用
        JSONObject result = RequestHelper.sendRequest(paramMap, HfbConst.MAKE_LOAD_URL);
        log.info("放款结果：" + result.toJSONString());

        //放款失败
        if (!"0000".equals(result.getString("resultCode"))) {
            log.info("汇付宝放款失败，放款失败描述：" + result.getString("resultMsg"));
            throw new BusinessException(result.getString("resultMsg"));
        }

        log.info("3 更新标的信息，汇付宝放款成功，开始修改尚融宝标的信息(lend表)，标的状态：还款中，设置平台实际收益");
        // 2、修改标的信息lend表，放款
        lend.setStatus(LendStatusEnum.PAY_RUN);
        lend.setRealAmount(realAmount);
        lend.setPaymentAdminId(1L);
        lend.setPaymentTime(LocalDateTime.now());// 放款时间
        baseMapper.updateById(lend);


        UserInfo userInfo = userInfoMapper.selectById(lend.getUserId());
        String bindCode = userInfo.getBindCode();

        log.info("4 更新借款人账户，生成流水，(user_account表)");
        // 3、修改user_account表，给借款人账户转入金额
        BigDecimal voteAmt = new BigDecimal(result.getString("voteAmt"));// 放款金额
        userAccountMapper.updateAccount(bindCode, voteAmt, new BigDecimal(0));
        log.info("借款人到账：" + voteAmt);


        log.info("生成借款人到账流水");
        // 4、新增借款人交易流水
        TransFlowBO transFlowBO = TransFlowBO.builder()
                .agentBillNo(agentBillNo)
                .bindCode(bindCode)
                .amount(voteAmt)
                .transTypeEnum(TransTypeEnum.BORROW_BACK)
                .memo("借款放款到账，编号：" + lend.getLendNo())
                .build();
        transFlowService.saveTransFlow(transFlowBO);

        log.info("5 循环更新投资人账户，生成流水");
        // 获取投资列表信息
        List<LendItem> lendItemList = lendItemService.getByLendId(lendId, 1);

        lendItemList.forEach(lendItem -> {

            // 获取投资人信息
            UserInfo investUserInfo = userInfoMapper.selectById(lendItem.getInvestUserId());
            String investBindCode = investUserInfo.getBindCode();

            // 投资人账号冻结金额转出
            log.info("扣减投资人：" + lendItem.getInvestName() + "的账户：" + lendItem.getInvestAmount());
            BigDecimal investAmount = lendItem.getInvestAmount(); // 投资金额
            userAccountMapper.updateAccount(investBindCode, new BigDecimal("0"), investAmount.negate());

            log.info("添加投资人：" + lendItem.getInvestName() + "的扣款流水");
            //新增投资人交易流水
            TransFlowBO investTransFlowBO = TransFlowBO.builder()
                    .agentBillNo(LendNoUtils.getTransNo())
                    .bindCode(investBindCode)
                    .amount(investAmount)
                    .transTypeEnum(TransTypeEnum.INVEST_UNLOCK)
                    .memo("冻结资金转出，出借放款，编号：" + lend.getLendNo())
                    .build();
            transFlowService.saveTransFlow(investTransFlowBO);
        });

        log.info("6 调用还款和回款程序");
        // 放款成功生成借款人还款计划和投资人回款计划 TODO
        repaymentPlan(lend, lendItemList);


    }

    /**
     * 生成还款计划
     *
     * @param lend         标的对象
     * @param lendItemList 投资人列表详情
     */
    private void repaymentPlan(Lend lend, List<LendItem> lendItemList) {

        log.info("第二步，进入还款计划流程");

        Integer period = lend.getPeriod();// 还款期数
        log.info("根据期数生成还款计划");

        // 有多少期就生成多少期还款计划
        for (int i = 1; i <= period; i++) {

            LendReturn lendReturn = new LendReturn();

            log.info("...生成第" + i + "期还款计划");
            if (i == period) {
                lendReturn.setLast(true);// 最后一期
            } else {
                lendReturn.setLast(false);// 不是最后一期
            }
            lendReturn.setUserId(lend.getUserId());// 借款人id
            lendReturn.setAmount(lend.getAmount());// 借款金额
            lendReturn.setBaseAmount(lend.getInvestAmount());// 计息本金
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());// 借款信息id
            lendReturn.setCurrentPeriod(i);// 当前期数
            lendReturn.setFee(new BigDecimal("0"));// 手续费，已一次性全部收取
            lendReturn.setLendId(lend.getId());// 标的id
            lendReturn.setLendYearRate(lend.getLendYearRate());// 年化利率
            lendReturn.setOverdue(false);// 是否预期
            lendReturn.setReturnMethod(lend.getReturnMethod().getValue());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());// 还款计划单号
            lendReturn.setOverdueTotal(new BigDecimal("0"));// 预期金额
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));// 第二个月开始还款
            lendReturn.setStatus(0);// 未还款
            lendReturn.setCreateTime(LocalDateTime.now());
            lendReturnMapper.insert(lendReturn);

            BigDecimal monthInterest = new BigDecimal("0");// 当期利息和 = 投资人利息和
            BigDecimal monthPrincipal = new BigDecimal("0");// 当期本金和 = 投资人本金和

            // 每生成一起还款计划，根据投资人生成响应的回款计划
            for (LendItem lendItem : lendItemList) {
                log.info("...生成第" + i + "期的" + lendItem.getInvestName() + "的回款计划");
                // 将该投资人本期(i)生成的本级和利息的数额返回
                Map<String, BigDecimal> investReturnMap = returnInvest(lend, lendItem, lendReturn, i);
                monthInterest = monthInterest.add(investReturnMap.get("interest"));
                monthPrincipal = monthPrincipal.add(investReturnMap.get("principal"));
            }

            lendReturn.setInterest(monthInterest);// 当期利息和 = 投资人利息和
            lendReturn.setPrincipal(monthPrincipal);// 当期本金和 = 投资人本金和
            lendReturn.setTotal(monthInterest.add(monthPrincipal));// 当前本息

            // 执行更新
            log.info("根据第" + i + "期,回款记录，更新本次还款的本金和利息");
            lendReturnMapper.updateById(lendReturn);
        }

    }

    /**
     * 生成投资人回款计划表，并将该投资人本期(i)生成的本金和利息的数额返回回来
     *
     * @param lend       标的信息
     * @param lendItem   投资人信息
     * @param lendReturn 借款人还款计划
     * @param i          当前期数
     * @return java.util.Map<java.lang.String, java.math.BigDecimal>
     */
    public Map<String, BigDecimal> returnInvest(Lend lend, LendItem lendItem, LendReturn lendReturn, int i) {

        log.info("第三步，进入回款计划流程");
        Map<String, BigDecimal> investReturnMap = new HashMap<>();

        LendItemReturn lendItemReturn = new LendItemReturn();// 回款计划
        lendItemReturn.setCreateTime(LocalDateTime.now());
        lendItemReturn.setCurrentPeriod(i);// 当前期数
        lendItemReturn.setFee(new BigDecimal("0"));// 手续费
        lendItemReturn.setInvestAmount(lendItem.getInvestAmount());// 投资金额
        lendItemReturn.setInvestUserId(lendItem.getInvestUserId());// 投资人id
        lendItemReturn.setLendId(lend.getId());
        lendItemReturn.setLendItemId(lendItem.getId());
        lendItemReturn.setLendReturnId(lendReturn.getId());
        lendItemReturn.setLendYearRate(lend.getLendYearRate());
        lendItemReturn.setOverdue(false);
        lendItemReturn.setOverdueTotal(new BigDecimal("0"));
        lendItemReturn.setReturnMethod(lend.getReturnMethod().getValue());
        lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));//还款日期
        lendItemReturn.setStatus(0);// 未归还

        // 计算第i期，该投资人的回款信息：本金和利息
        Map<Integer, BigDecimal> perMonthInterest;
        Map<Integer, BigDecimal> perMonthPrincipal;


        AmountHelper amountHelper = dispatcher.getAmountHelper(lend.getReturnMethod().getReturnMethod());
        AmountHelperAdapter amountHelperAdapter = dispatcher.getAmountHelperAdapter(amountHelper);

        perMonthInterest = amountHelperAdapter
                .getPerMonthInterest(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
        perMonthPrincipal = amountHelperAdapter
                .getPerMonthPrincipal(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());

        // 判断还款方式
//        if (lend.getReturnMethod().equals(ReturnMethodEnum.EQUALITY_CORPUS_AND_INTEREST)) {
//            // 第一种，等额本息
//            perMonthInterest = Amount1Helper.getPerMonthInterest(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//            perMonthPrincipal = Amount1Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//        }
//
//        if (lend.getReturnMethod().equals(ReturnMethodEnum.EQUALITY_CORPUS)) {
//            // 第二种，等额本金
//            perMonthInterest = Amount2Helper.getPerMonthInterest(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//            perMonthPrincipal = Amount2Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//        }
//
//        if (lend.getReturnMethod().equals(ReturnMethodEnum.PER_MONTH_INTEREST_AND_ONCE_CORPUS)) {
//            // 第三种，每月还息一次还本
//            perMonthInterest = Amount3Helper.getPerMonthInterest(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//            perMonthPrincipal = Amount3Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//        }
//
//        if (lend.getReturnMethod().equals(ReturnMethodEnum.ONCE_CORPUS_AND_INTEREST)) {
//            // 第四种，一次还本还息
//            perMonthInterest = Amount4Helper.getPerMonthInterest(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//            perMonthPrincipal = Amount4Helper.getPerMonthPrincipal(lendItem.getInvestAmount(), lend.getLendYearRate(), lend.getPeriod());
//        }

        lendItemReturn.setPrincipal(perMonthPrincipal.get(i));
        lendItemReturn.setInterest(perMonthInterest.get(i));
        lendItemReturn.setTotal(perMonthPrincipal.get(i).add(perMonthInterest.get(i)));
        lendItemReturnMapper.insert(lendItemReturn);

        log.info("计算" + lendItem.getInvestName() + "在第" + i + "期收到本金和利息，放入map返回结果");

        investReturnMap.put("interest", perMonthInterest.get(i));// 为计算还款计划的利息和
        investReturnMap.put("principal", perMonthPrincipal.get(i));// 为了计算还款计划的本金和

        return investReturnMap;
    }

}
